/*
 * Copyright (C) 2012 Andrea Parodi
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package container;

import com.google.common.base.Optional;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;

import java.util.List;

public final class ContainerNumber {

    public static final String CONTAINER_NUMBERS_FORMAT_ERRROR = "Container numbers must be composed by 4 letters followed by 7 digits.";
    public static final String NON_STANDARD_4_LETTER = "Container number fourth letter should be one of U,J or Z.";
    public static final String BAD_CHECK_DIGIT = "Bad final check digit <%d>: should be <%d>.";
    public static final String CHECK_DIGIT_0 = "ISO standard recommends that serial numbers should not be used which produce a check digit of <0>.";
    public static final String UNABLE_TO_CALCULATE_CHECK_DIGIT = "Unable to calculate check digit.";

    public String getNumber() {
        return number;
    }

    private final String number;

    @Override public boolean equals(Object that) {
        if (that == null) return false;
        if (that.getClass() != ContainerNumber.class) return false;
        return this.number.equals (((ContainerNumber) that).number );
    }

    @Override public int hashCode() {
        return number.hashCode();
    }

    @Override public String toString() {
        return number;
    }

    public static ContainerNumber fromString(String number) {
        return new ContainerNumber(number);
    }

    private ContainerNumber(String cntrNumber) {
        Preconditions.checkNotNull(cntrNumber);
        number = stripSeparators(cntrNumber);
        validationResult = validate(number);
    }

    private String stripSeparators(String number) {
        StringBuilder builder = new StringBuilder(11);
        char c;
        for (int i = 0; i < number.length(); i++) {
            c = number.charAt(i);
            if (!ContainerNumbersParser.isSeparator(c))
                builder.append(Character.toUpperCase(c));
        }
        return builder.toString();
    }

    private ValidationResults validate(String number) {
        List<ValidationMessage> results = Lists.newArrayList();
        if (number.length() != 11)
            results.add(ValidationMessage.newError(CONTAINER_NUMBERS_FORMAT_ERRROR));


        for (int i = 0; i < 4; i++)
            if (!Character.isLetter(number.charAt(i)))
                results.add(ValidationMessage.newError(CONTAINER_NUMBERS_FORMAT_ERRROR));

        for (int i = 4; i < 11; i++)
            if (!Character.isDigit(number.charAt(i)))
                results.add(ValidationMessage.newError(CONTAINER_NUMBERS_FORMAT_ERRROR));

        final char letter4 = number.charAt(3);
        if (letter4 != 'U' && letter4 != 'Z' && letter4 != 'J')
            results.add(ValidationMessage.newWarning(NON_STANDARD_4_LETTER));

        final Integer actualCheckDigit = Integer.valueOf(number.substring(10));
        final Optional<Integer> expectedCheckDigit = CheckDigitCalculator.getCheckDigit(number.substring(0, 10));
        if (!expectedCheckDigit.isPresent())
            results.add(ValidationMessage.newError(UNABLE_TO_CALCULATE_CHECK_DIGIT));
        else if (!expectedCheckDigit.get().equals(actualCheckDigit))
            results.add(ValidationMessage.newError(String.format(BAD_CHECK_DIGIT, actualCheckDigit, expectedCheckDigit.get())));

        if (actualCheckDigit == 0)
            results.add(ValidationMessage.newWarning(CHECK_DIGIT_0));


        return ValidationResults.from(results);
    }

    public boolean isValid() {
        return validationResult.getMessages().isEmpty();
    }


    public ValidationResults getValidationResult() {
        return validationResult;
    }

    private final ValidationResults validationResult;

}
